package main

import (
	"database/sql"
	"fmt"
	"log"
	"os"

	_ "github.com/go-sql-driver/mysql"
)

//sql.Open("mysql", "username:pwd@/databasename")
//功能：返回一个DB对象，DB对象对于多个goroutines并发使用是安全的，DB对象内部封装了连接池。
//实现：open函数并没有创建连接，它只是验证参数是否合法。然后开启一个单独goroutines去监听是否需要建立新的连接，当有请求建立新连接时就创建新连接。
//注意：1.open函数应该被调用一次，通常是没必要close的。
//     2.Open返回的 DB 并非一个实际的到数据库的连接.
//     3.Open可以被多个 goroutine 并发使用,因此，程序中只需要拥有一个全局的实例即可
//     4.要验证 dataSourceName 是否合法,使用 sql.Ping 函数,即使err == nil 仍无法判断 dataSourceName是否合法.
//     5.sql.DB的设计就是用来作为长连接使用的。不要频繁Open, Close。比较好的做法是，为每个不同的datastore建一个DB对象，保持这些对象Open。如果需要短连接，那么把DB作为参数传入function，而不要在function中Open, Close。

//（2）DB.Exec()
//功能：执行不返回行（row）的查询，比如INSERT，UPDATE，DELETE
//实现：DB交给内部的exec方法负责查询。exec会首先调用DB内部的conn方法从连接池里面获得一个连接。然后检查内部的driver.Conn实现了Execer接口没有，如果实现了该接口，会调用Execer接口的Exec方法执行查询；否则调用Conn接口的Prepare方法负责查询。

//(3) DB.Query()
//功能:用于检索(retrieval),比如SELECT
//实现:DB交给内部的query方法负责查询.query首先调用DB内部的conn方法从连接池里面获得一个连接,后调用内部的queryConn方法负责查询.

//（4）DB.QueryRow()
//功能：用于返回单行的查询
//实现：转交给DB.Query()查询

//（5）db.Prepare()
//
//功能：返回一个Stmt。Stmt对象可以执行Exec,Query,QueryRow等操作。
//
//实现：DB交给内部的prepare方法负责查询。prepare首先调用DB内部的conn方法从连接池里面获得一个连接，然后调用driverConn的prepareLocked方法负责查询。
//Stmt相关方法：
//st.Exec()
//st.Query()
//st.QueryRow()
//st.Close()

//（6）row.Close()
//
// 结果集(rows)未关闭前，底层的连接处于繁忙状态。当遍历读到最后一条记录时，会发生一个内部EOF错误，
// 自动调用rows.Close()，但是如果提前退出循环，rows不会关闭，连接不会回到连接池中，连接也不会关闭。
// 所以手动关闭非常重要。rows.Close()可以多次调用，是无害操作

//在Transaction中使用PS
//PS在Tx中唯一绑定一个连接，不会re-prepare。
//
//Tx和statement不能分离，在DB中创建的statement也不能在Tx中使用，因为他们必定不是使用同一个连接使用Tx必须十分小心.

var (
	ip      string = "192.168.254.128"
	port    uint   = 3306
	user    string = "root"
	passwd  string = "000000"
	db_name string = "mail"
)

func main() {
	//          [username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]
	//dsn 字符串填写: "username:passowrd@protocol(localhost:port)/dbname?charset=utf8&dbcollation=utf8_general_ci"
	//示例 :
	// 1. root:@/test?charset=utf8
	// 2. root:000000@(192.168.0.119:3306)/mail?charset=utf8&collation=utf8_general_ci&loc=Asia%2FShanghai
	// 3. dbcollat=utf8_general_ci  在mysql使用会报错
	// 4. user:password@tcp(127.0.0.1:3306)/hello
	// 5. dsn字符串可以使用 Config.FormatDSN 获取到
	//    dsn := (&mysql.Config{
	//        User:user,
	//        Passwd:passwd,
	//        Net:"tcp",
	//        Addr:fmt.Sprintf("%s:%d",ip,port),
	//        Collation:"utf8_general_ci",
	//        DBName:db_name,
	//    }).FormatDSN()
	dsn := fmt.Sprintf("%s:%s@(%s:%d)/(tcp)%s%s", user, passwd, ip, port, db_name, "?charset=utf8&loc=Asia%2FShanghai")
	//db, err := sql.Open("mysql", "root:000000@(192.168.0.119:3306)/mail?charset=utf8&loc=Asia%2FShanghai")
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		fmt.Println("err in Use of db.Open:", err.Error())
		os.Exit(-1)
	}
	fmt.Printf("\nExample ---> use of db.Ping <--- \n")
	err = db.Ping()
	if err != nil {
		fmt.Println("err in Use of db.Ping:", err.Error())
		os.Exit(-1)
	}
	fmt.Printf("\nExample ---> use of db.Exec <--- \n")
	res, err := db.Exec("insert into user(username,userpwd) values(?,?)", "zhangfei", "000000")
	if err != nil {
		fmt.Println("err in Use of db.Exec:", err.Error())
	}

	l, err := res.LastInsertId()
	if err != nil {
		fmt.Println("err in Use of db.Exec [ LastInsertId ] :", err.Error())
	}
	fmt.Println("LastInsetId:", l)

	r, err := res.RowsAffected()
	if err != nil {
		fmt.Println("err in Use of db.Exec [ RowsAffected ] :", err.Error())
	}
	fmt.Println("LastInsetId:", r)

	fmt.Printf("\nExample ---> use of db.Query <--- \n")
	//1.db.Query() 等同于 SQL的select,查询不到数据 err = nil
	//2.比较db.QueryRow()
	row, err := db.Query("select username from user where userpwd = ?", "000000")
	//row,err:= db.Query("select username from user where userpwd = ?","111111")
	if err != nil {
		fmt.Println("err in Use of db.Query:", err.Error())
	}
	defer row.Close()
	for row.Next() {
		var name string
		if err = row.Scan(&name); err != nil {
			fmt.Println("err in row.Scon:", err.Error())
		}
		fmt.Println("Query:username:", name)
	}
	if err := row.Err(); err != nil {
		fmt.Println("err in row.Err():", err.Error())
	}
	row.Close()

	fmt.Printf("\nExample ---> use of db.QueryRow <--- \n")
	var name string
	//1.QueryRow 返回非空数据，查询不到结果报错,错误信息 需要调用 rows.Scan 获取
	rows := db.QueryRow("select username from user where userpwd = ?", "000000")
	//rows := db.QueryRow("select username from user where userpwd = ?", "111111")
	//1.SCan 如果有多条数据匹配，返回第一条，其余舍弃
	//2.查询不到数据，报错 sql: no rows in result set
	err = rows.Scan(&name)
	if err != nil {
		fmt.Println("err in db.QueryRow():", err.Error())
	}
	fmt.Println("QueryRow:username", name)
	err = rows.Scan(&name)

	fmt.Printf("\nExample ---> use of db.Prepare <--- \n")
	userpwd := "000000"
	stmt, err := db.Prepare("SELECT username FROM user WHERE userpwd = ?")
	if err != nil {
		log.Fatal(err)
	}

	//1.stmt.Query,stmt.QueryRow 和 DB.QUery,DB.QUeryRow 一样理解
	row, err = stmt.Query(userpwd)
	//以下代码可忽略,参考db.Query如何处理
	//if err != nil {
	//	fmt.Println("err in Use of stmt.Query:", err.Error())
	//}
	//for row.Next() {
	//	var name string
	//	if err = row.Scan(&name); err != nil {
	//		fmt.Println("err in row.Scon:", err.Error())
	//	}
	//	fmt.Println("Query:username:", name)
	//}
	//if err := row.Err(); err != nil {
	//	fmt.Println("err in row.Err():", err.Error())
	//}
	//row.Close()

	fmt.Printf("\nExample ---> use of db.Begin <--- \n")
	//1.mysql 事物处理, Begin  -- Exec -- Commit
	tx, err := db.Begin()
	res, err = tx.Exec("insert into user(username,userpwd) values(?,?)", "wangba", "111111")
	//if tx.Commit() != nil {
	//    fmt.Println("err in db.Begin[ tx.Commit ]")
	//}
	r, _ = res.LastInsertId()
	aff, _ := res.RowsAffected()
	fmt.Println("Db.Begin result,LastInsetId:", r, "RowsAffected:", aff)
}
